////////////////////////////////////////////////////////////////////////////
//
//  CryEngine Source File.
//  Copyright (C), Crytek, 1999-2010.
// -------------------------------------------------------------------------
//  File name:   VisRegTest.cpp
//  Version:     v1.00
//  Created:     14/07/2010 by Nicolas Schulz.
//  Description: Visual Regression Test
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "VisRegTest.h"

#include "ISystem.h"
#include "I3DEngine.h"
#include "IRenderer.h"
#include "IConsole.h"
#include "ITimer.h"
#include "IInput.h"
#include "IEntitySystem.h"
#include "IStreamEngine.h"


CVisRegTest::CVisRegTest() : m_nextCmd( 0 ), m_waitFrames( 0 )
{
	CryLog( "Enabled visual regression tests" );
}


void CVisRegTest::Init( IConsoleCmdArgs *pParams )
{
	assert( pParams );
	
	stack_string configFile( "visregtest.xml" );
	
	// Reset
	m_cmdBuf.clear();
	m_dataSamples.clear();
	m_nextCmd = 0;
	m_cmdFreq = 0;
	m_waitFrames = 0;
	m_testName = "test";
	m_quitAfterTests = false;

	// Parse arguments
	if( pParams->GetArgCount() >= 2 )
	{
		m_testName = pParams->GetArg( 1 );
	}
	if( pParams->GetArgCount() >= 3 )
	{
		configFile = pParams->GetArg( 2 );
	}
	if( pParams->GetArgCount() >= 4 )
	{
		if( _stricmp( pParams->GetArg( 3 ), "quit" ) == 0 )
			m_quitAfterTests = true;
	}
	
	// Fill cmd buffer
	if( !LoadConfig( configFile.c_str() ) )
	{
		CryWarning( VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, "VisRegTest: Failed to load config file '%s' from game folder", configFile.c_str() );
		return;
	}

	gEnv->pTimer->SetTimeScale( 0 );
	GetISystem()->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_RANDOM_SEED, 0, 0 );
	srand( 0 );

	gEnv->pRenderer->EnableGPUTimers2( true );
}


void CVisRegTest::AfterRender()
{
	ExecCommands();
}


bool CVisRegTest::LoadConfig( const char *configFile )
{
	XmlNodeRef rootNode = GetISystem()->LoadXmlFromFile( PathUtil::GetGameFolder() + "/" + configFile );
	if( !rootNode || !rootNode->isTag( "VisRegTest" ) ) return false;

	m_cmdBuf.push_back( SCmd( eCMDStart, "" ) );

	for( int i = 0; i < rootNode->getChildCount(); ++i )
	{
		XmlNodeRef node = rootNode->getChild( i );

		if( node->isTag( "Config" ) )
		{
			SCmd cmd;
			for( int j = 0; j < node->getChildCount(); ++j )
			{
				XmlNodeRef node2 = node->getChild( j );
				
				if( node2->isTag( "ConsoleCmd" ) )
				{
					m_cmdBuf.push_back( SCmd( eCMDConsoleCmd, node2->getAttr( "cmd" ) ) );
				}
			}
		}
		else if( node->isTag( "Map" ) )
		{
			const char *mapName = node->getAttr( "name" );
			int imageIndex = 0;

			m_cmdBuf.push_back( SCmd( eCMDLoadMap, mapName ) );
			m_cmdBuf.push_back( SCmd( eCMDOnMapLoaded, "" ) );

			for( int j = 0; j < node->getChildCount(); ++j )
			{
				XmlNodeRef node2 = node->getChild( j );
				
				if( node2->isTag( "ConsoleCmd" ) )
				{
					m_cmdBuf.push_back( SCmd( eCMDConsoleCmd, node2->getAttr( "cmd" ) ) );
				}
				else if( node2->isTag( "Sample" ) )
				{
					m_cmdBuf.push_back( SCmd( eCMDGoto, node2->getAttr( "location" ) ) );
					m_cmdBuf.push_back( SCmd( eCMDWaitStreaming, "" ) );
					
					char strbuf[256];
					sprintf( strbuf, "%s_%i.tga", mapName, imageIndex++ );
					m_cmdBuf.push_back( SCmd( eCMDCaptureSample, strbuf, SampleCount ) );
				}
			}
		}
	}

	m_cmdBuf.push_back( SCmd( eCMDFinish, ""  ) );

	return true;
}


void CVisRegTest::ExecCommands()
{	
	if( m_nextCmd >= m_cmdBuf.size() ) return;

	float col[] = {0, 1, 0, 1};
	gEnv->pRenderer->Draw2dLabel( 10, 10, 2, col, false, "Visual Regression Test" );

	if( m_waitFrames > 0 )
	{
		--m_waitFrames;
		return;
	}
	else if( m_waitFrames < 0 )  // Wait for streaming
	{
		IStreamEngine::SStatistics &stats = gEnv->pSystem->GetStreamEngine()->GetStreamingStatistics();
		if( stats.nOpenRequestCount > 0 )
		{
			gEnv->pConsole->ExecuteString( "t_FixedStep 0" );
			m_waitFrames = -16;
		}
		else if( ++m_waitFrames == 0 )
		{
			gEnv->pConsole->ExecuteString( "t_FixedStep 0.033333" );
			m_waitFrames = 64;  // Wait some more frames for tone-mapper to adapt
		}

		return;
	}

	stack_string tmp;
	
	while( m_nextCmd < m_cmdBuf.size() )
	{
		SCmd &cmd = m_cmdBuf[m_nextCmd];
		
		if( m_cmdFreq == 0 )
			m_cmdFreq = cmd.freq;

		switch( cmd.cmd )
		{
		case eCMDStart:
			break;
		case eCMDFinish:
			Finish();
			break;
		case eCMDOnMapLoaded:
			gEnv->pTimer->SetTimer( ITimer::ETIMER_GAME, 0 );
			gEnv->pTimer->SetTimer( ITimer::ETIMER_UI, 0 );
			gEnv->pConsole->ExecuteString( "t_FixedStep 0.033333" );
			GetISystem()->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_RANDOM_SEED, 0, 0 );
			srand( 0 );
			break;
		case eCMDConsoleCmd:
			gEnv->pConsole->ExecuteString( cmd.args.c_str() );
			break;
		case eCMDLoadMap:
			LoadMap( cmd.args.c_str() );
			break;
		case eCMDWaitStreaming:
			m_waitFrames = -1;
			break;
		case eCMDWaitFrames:
			m_waitFrames = (uint32)atoi( cmd.args.c_str() );
			break;
		case eCMDGoto:
			tmp = "playerGoto ";
			tmp.append( cmd.args.c_str() );
			gEnv->pConsole->ExecuteString( tmp.c_str() );
			m_waitFrames = 1;
			break;
		case eCMDCaptureSample:
			CaptureSample( cmd );
			break;
		}

		if( m_cmdFreq-- == 1 )
			++m_nextCmd;
		
		if( m_waitFrames != 0 ) break;
	}
}


void CVisRegTest::LoadMap( const char *mapName )
{
	string mapCmd( "map " );
	mapCmd.append( mapName );
	gEnv->pConsole->ExecuteString( mapCmd.c_str() );

	gEnv->pTimer->SetTimer( ITimer::ETIMER_GAME, 0 );
	gEnv->pTimer->SetTimer( ITimer::ETIMER_UI, 0 );
	gEnv->pConsole->ExecuteString( "t_FixedStep 0" );
	GetISystem()->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_RANDOM_SEED, 0, 0 );
	srand( 0 );

	// Disable user input
	gEnv->pInput->EnableDevice( eDI_Keyboard, false );
	gEnv->pInput->EnableDevice( eDI_Mouse, false );
	gEnv->pInput->EnableDevice( eDI_XI, false );
}


void CVisRegTest::CaptureSample( const SCmd &cmd )
{
	if( m_cmdFreq == SampleCount )  // First sample
	{
		m_dataSamples.push_back( SSample() );
		gEnv->pConsole->ExecuteString( "t_FixedStep 0" );
	}

	SSample &sample = m_dataSamples.back();
	
	// Collect stats
	sample.imageName = cmd.args.c_str();
	sample.frameTime += gEnv->pTimer->GetRealFrameTime() * 1000.f;
	sample.drawCalls += gEnv->pRenderer->GetCurrentNumberOfDrawCalls();

	sample.gpuTimes[0] += gEnv->pRenderer->GetGPUTimer2( "SCENE" );

	sample.gpuTimes[1] += gEnv->pRenderer->GetGPUTimer2( "SHADOWMAPS" ) + gEnv->pRenderer->GetGPUTimer2( "SHADOWMASK" );
	
	sample.gpuTimes[2] += gEnv->pRenderer->GetGPUTimer2( "ZPASS" );
	sample.gpuTimes[3] += gEnv->pRenderer->GetGPUTimer2( "DEFERRED_DECALS" );

	sample.gpuTimes[4] += gEnv->pRenderer->GetGPUTimer2( "DEFERRED_LIGHTING" );
	sample.gpuTimes[5] += gEnv->pRenderer->GetGPUTimer2( "DL_AMBIENT" );
	sample.gpuTimes[6] += gEnv->pRenderer->GetGPUTimer2( "DL_CUBEMAPS" );
	sample.gpuTimes[7] += gEnv->pRenderer->GetGPUTimer2( "DL_LIGHTS0" ) + gEnv->pRenderer->GetGPUTimer2( "DL_LIGHTS1" );
	sample.gpuTimes[8] += gEnv->pRenderer->GetGPUTimer2( "GI" ) + gEnv->pRenderer->GetGPUTimer2( "SSAO" ) + gEnv->pRenderer->GetGPUTimer2( "SSAO_APPLY" );
	sample.gpuTimes[9] += gEnv->pRenderer->GetGPUTimer2( "OPAQUE" );

#ifndef XENON
	sample.gpuTimes[9] -= gEnv->pRenderer->GetGPUTimer2( "SHADOWMASK" );  // Shadow mask updated during opaque pass
#endif

	sample.gpuTimes[10] += gEnv->pRenderer->GetGPUTimer2( "TRANSPARENT" );
	sample.gpuTimes[11] += gEnv->pRenderer->GetGPUTimer2( "HDR" );
	sample.gpuTimes[12] += gEnv->pRenderer->GetGPUTimer2( "POSTFX" );
	
	if( m_cmdFreq == 1 )  // Final sample
	{
		// Screenshot
		stack_string filename( "TestResults/VisReg/" );
		filename += m_testName + "/" + cmd.args.c_str();
		gEnv->pRenderer->ScreenShot( filename );

		// Average results
		sample.frameTime /= (float)SampleCount;
		sample.drawCalls /= SampleCount;
		for( uint32 i = 0; i < MaxNumGPUTimes; ++i )
			sample.gpuTimes[i] /= (float)SampleCount;

		gEnv->pConsole->ExecuteString( "t_FixedStep 0.033333" );
	}
}


void CVisRegTest::Finish()
{
	WriteResults();

	gEnv->pInput->EnableDevice( eDI_Keyboard, true );
	gEnv->pInput->EnableDevice( eDI_Mouse, true );
	gEnv->pInput->EnableDevice( eDI_XI, true );

	gEnv->pConsole->ExecuteString( "t_FixedStep 0" );
	gEnv->pTimer->SetTimeScale( 1 );

	gEnv->pRenderer->EnableGPUTimers2( false );

	CryLog( "VisRegTest: Finished tests" );

	if( m_quitAfterTests )
	{
		gEnv->pConsole->ExecuteString( "quit" );
		return;
	}
}


bool CVisRegTest::WriteResults()
{
	stack_string filename( "TestResults/VisReg/" );
	filename += m_testName + "/visreg_results.xml";
	













	
	FILE *f = fxopen( filename.c_str(), "wb" );
	if( !f )
		return false;

	fprintf( f, "<testsuites>\n" );
	fprintf( f, "\t<testsuite name=\"Visual Regression Test\" LevelName=\"\">\n" );
	
	for( int i = 0; i < (int)m_dataSamples.size(); ++i )
	{
		SSample sample = m_dataSamples[i];
		
		fprintf( f, "\t\t<phase name=\"%i\" duration=\"1\" image=\"%s\">\n", i, sample.imageName );
	
		fprintf( f, "\t\t\t<metrics name=\"general\">\n" );
		fprintf( f, "\t\t\t\t<metric name=\"frameTime\" value=\"%f\" />\n", sample.frameTime );
		fprintf( f, "\t\t\t\t<metric name=\"drawCalls\" value=\"%i\" />\n", sample.drawCalls );
		fprintf( f, "\t\t\t</metrics>\n" );

		fprintf( f, "\t\t\t<metrics name=\"gpu_times\">\n" );
		fprintf( f, "\t\t\t\t<metric name=\"scene\" value=\"%f\" />\n", sample.gpuTimes[0] );
		fprintf( f, "\t\t\t\t<metric name=\"shadows\" value=\"%f\" />\n", sample.gpuTimes[1] );
		fprintf( f, "\t\t\t\t<metric name=\"zpass\" value=\"%f\" />\n", sample.gpuTimes[2] );
		fprintf( f, "\t\t\t\t<metric name=\"deferred_decals\" value=\"%f\" />\n", sample.gpuTimes[3] );
		fprintf( f, "\t\t\t\t<metric name=\"deferred_lighting\" value=\"%f\" />\n", sample.gpuTimes[4] );
		fprintf( f, "\t\t\t\t<metric name=\"dl_ambient\" value=\"%f\" />\n", sample.gpuTimes[5] );
		fprintf( f, "\t\t\t\t<metric name=\"dl_cubemaps\" value=\"%f\" />\n", sample.gpuTimes[6] );
		fprintf( f, "\t\t\t\t<metric name=\"dl_lights\" value=\"%f\" />\n", sample.gpuTimes[7] );
		fprintf( f, "\t\t\t\t<metric name=\"gi_and_ssao\" value=\"%f\" />\n", sample.gpuTimes[8] );
		fprintf( f, "\t\t\t\t<metric name=\"opaque\" value=\"%f\" />\n", sample.gpuTimes[9] );
		fprintf( f, "\t\t\t\t<metric name=\"transparent\" value=\"%f\" />\n", sample.gpuTimes[10] );
		fprintf( f, "\t\t\t\t<metric name=\"hdr\" value=\"%f\" />\n", sample.gpuTimes[11] );
		fprintf( f, "\t\t\t\t<metric name=\"postfx\" value=\"%f\" />\n", sample.gpuTimes[12] );
		fprintf( f, "\t\t\t</metrics>\n" );
		
		fprintf( f, "\t\t</phase>\n" );
	}

	fprintf( f, "\t</testsuite>\n" );
	fprintf( f, "</testsuites>" );
	fclose( f );

	return true;
}
